---
title: 版面
description: Astro 中版面的簡介。
i18nReady: true
---

import ReadMore from '~/components/ReadMore.astro'

**版面**是一種 [Astro 元件](/zh-tw/basics/astro-components/)，用來建立可重複使用的 UI 結構，例如頁面模板。

我們習慣以「版面」稱呼在不同頁面共用的 Astro 元件，例如頁首、導覽列、頁尾這種 UI 元素。典型的 Astro 版面元件為 [Astro、Markdown 或 MDX 頁面](/zh-tw/basics/astro-pages/)提供：

- **頁面殼層** （`<html>` 、 `<head>` 和 `<body>` 標籤）
- 供頁面內容嵌入的插槽 [**`<slot />`**](/zh-tw/basics/astro-components/#插槽)

但其實版面元件沒什麼特別的！它們和其他 Astro 元件一樣，可以[接受參數](/zh-tw/basics/astro-components/#元件參數)、[匯入並使用其他元件](/zh-tw/basics/astro-components/#元件架構)，也能包含 [UI 框架元件](/zh-tw/guides/framework-components/)和[客戶端腳本](/zh-tw/guides/client-side-scripts/)。甚至可當作局部 UI 模板，不需要提供整個頁面。

然而，如果版面元件有包含頁面殼層，它的 `<html>` 元素必須是元件裡其他元素的父元素。

版面元件通常放在專案的 `src/layouts` 目錄，但這不是強制規定，可以自由選擇要放在哪裡。你甚至可以把它們跟頁面放在一起，只要[在版面名稱加上 `_` 前綴](/zh-tw/guides/routing/#excluding-pages)即可。

## 版面範例

```astro "<slot />"
---
// src/layouts/MySiteLayout.astro
import BaseHead from '../components/BaseHead.astro';
import Footer from '../components/Footer.astro';
const { title } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <BaseHead title={title}/>
  </head>
  <body>
    <nav>
      <a href="#">Home</a>
      <a href="#">Posts</a>
      <a href="#">Contact</a>
    </nav>
    <h1>{title}</h1>
    <article>
      <slot /> <!-- 你的內容會嵌在這裡 -->
    </article>
    <Footer />
  </body>
  <style>
    h1 {
      font-size: 2rem;
    }
  </style>
</html>
```

```astro title="src/pages/index.astro"
---
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout title="Home Page">
  <p>我的網頁內容被包在一個版面裡！</p>
</MySiteLayout>
```

<ReadMore>進一步了解[插槽](/zh-tw/basics/astro-components/#插槽)。</ReadMore>

## 在版面使用 TypeScript

所有 Astro 版面都可以藉由提供參數的型別來導入型別安全與自動完成：

```astro ins={2-7} title="src/components/MyLayout.astro"
---
interface Props { 
  title: string;
  description: string;
  publishDate: string;
  viewCount: number;
}
const { title, description, publishDate, viewCount } = Astro.props;
---
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="description" content={description}>
    <title>{title}</title>
  </head>
  <body>
    <header>
      <p>Published on {publishDate}</p>
      <p>Viewed by {viewCount} folks</p>
    </header>
    <main>
      <slot />
    </main>
  </body>
</html>
```

## Markdown 版面

版面對無法自訂頁面格式的 Markdown 頁面來說很實用。

Astro 特殊的 `layout` frontmatter 屬性是用來給[位於 `src/pages/` 中、使用基於檔案的路由的個別 `.md` 檔](/zh-tw/guides/markdown-content/#individual-markdown-pages)指定要把哪一個 `.astro` 元件當作頁面版面的。這個元件讓你可以為 Markdown 頁面提供樣式和 `<head>` 的內容，像是 meta 標籤（如 `<meta charset="utf-8">`）。這個指定的元件預設可以自動從 Markdown 檔案存取資料。

使用[內容合集](/zh-tw/guides/content-collections/)查詢與算繪內容的時候，它不會被當作特殊的屬性。

```markdown title="src/pages/page.md" {2}
---
layout: ../layouts/BlogPostLayout.astro
title: "Hello, World!"
author: "Matthew Phillips"
date: "09 Aug 2022"
---
Astro 版面元件可以透過參數存取所有 frontmatter 屬性。

`layout` 是 Astro 提供的特殊屬性。

`src/pages/` 的 Markdown 檔案可以使用該屬性。

```

給 Markdown 頁面用的版面通常包含：

1. `frontmatter` 參數，能夠存取 Markdown 頁面的 frontmatter 和其他資料。
2. 預設的 [`<slot />`](/zh-tw/basics/astro-components/#插槽)，指名頁面的 Markdown 內容要在哪個位置算繪。

```astro title="src/layouts/BlogPostLayout.astro" /(?<!//.*){?frontmatter(?:\\.\w+)?}?/ "<slot />"
---
// 1. 透過 frontmatter 參數存取 frontmatter 和其他資料
const { frontmatter } = Astro.props;
---
<html>
  <head>
    <!-- 這裡可以放其他 Head 元素，例如樣式和 meta 標籤。 -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta charset="utf-8">
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <!-- 這裡可以放其他 UI 元件，例如共用的頁首和頁尾。 -->
    <h1>{frontmatter.title} by {frontmatter.author}</h1>
    <!-- 2. 算繪後的 HTML 會傳入預設插槽 -->
    <slot />
    <p>Written on: {frontmatter.date}</p>
  </body>
</html>
```

你可藉由 `MarkdownLayoutProps` 設定版面的 [`Props` 型別](/zh-tw/guides/typescript/#component-props)：

```astro title="src/layouts/BlogPostLayout.astro" ins={2,4-9}
---
import type { MarkdownLayoutProps } from 'astro';

type Props = MarkdownLayoutProps<{
  // 在此定義 frontmatter 參數的型別
  title: string;
  author: string;
  date: string;
}>;

// 現在，`frontmatter`、`url` 與其他 Markdown 版面屬性
// 都能在保證型別安全的情況下存取
const { frontmatter, url } = Astro.props;
---
<html>
  <head>
    <link rel="canonical" href={new URL(url, Astro.site).pathname}>
    <title>{frontmatter.title}</title>
  </head>
  <body>
    <h1>{frontmatter.title} by {frontmatter.author}</h1>
    <slot />
    <p>Written on: {frontmatter.date}</p>
  </body>
</html>
```

### Markdown 版面參數

Markdown 版面能透過 `Astro.props` 存取下列資訊：

- **`file`**：檔案的絕對路徑 (例如 `/home/user/projects/.../file.md`)。
- **`url`**：頁面網址（例如 `/zh-tw/guides/markdown-content`）。
- **`frontmatter`**：Markdown 或 MDX 文件中的所有 frontmatter。
  - **`frontmatter.file`**：同最上層的 `file` 屬性。
  - **`frontmatter.url`**：同最上層的 `url` 屬性。
- **`headings`**：Markdown 或 MDX 文件中的標題（`h1 -> h6`）列表，包含對應的 metadata。其型別為 `{ depth: number; slug: string; text: string }[]`。
- **`rawContent()`**：取得 Markdown 原始內容的函式，回傳值格式為字串。
- **`compiledContent()`**：取得 Markdown 編譯後內容的非同步函式，回傳值格式為 HTML 字串。

:::note
Markdown 版面也能透過 `Astro.props` 存取 Markdown 檔案[可存取的屬性](/zh-tw/guides/markdown-content/#available-properties)，不過其中**有兩個差異**：

* 標題資訊（即 `h1 -> h6` 元素）透過 `headings` 陣列存取，而非 `getHeadings()` 函式。

* `file` 和 `url` *也*可在巢狀 `frontmatter` 屬性下存取（即 `frontmatter.url` 和 `frontmatter.file`）。

:::

### 手動匯入版面（MDX）

你也可以使用 MDX 檔案 frontmatter 中特殊的 Markdown layout 屬性直接傳遞 `frontmatter` 和 `headings` 參數到指定的版面元件。

需要傳遞資訊到 MDX 版面，但該版面不存在（或無法存在）frontmatter 時，可以匯入 `<Layout />` 元件。它就像其他 Astro 元件一樣，無法自動接收任何參數。直接將任何必要的參數傳遞給它：

```mdx title="src/pages/posts/first-post.mdx" ins={6} del={2} /</?BaseLayout>/ /</?BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>/
---
layout: ../../layouts/BaseLayout.astro
title: 'My first MDX post'
publishDate: '21 September 2022'
---
import BaseLayout from '../../layouts/BaseLayout.astro';

export function fancyJsHelper() {
  return "Try doing that with YAML!";
}

<BaseLayout title={frontmatter.title} fancyJsHelper={fancyJsHelper}>
  Welcome to my new Astro blog, using MDX!
</BaseLayout>
```

如此一來，版面便能透過 `Astro.props` 存取數值，而 MDX 內容則會嵌入到包含 `<slot />` 的頁面中：

```astro title="src/layouts/BaseLayout.astro" /{?title}?/ "fancyJsHelper" "{fancyJsHelper()}"
---
const { title, fancyJsHelper } = Astro.props;
---
<html>
  <head>
    <!-- -->
    <meta charset="utf-8">
  </head>
  <body>
    <!-- -->
    <h1>{title}</h1>
    <slot /> <!-- 你的內容會嵌在這裡 -->
    <p>{fancyJsHelper()}</p>
    <!-- -->
  </body>
</html>
```

不論是經由 frontmatter `layout` 屬性，還是匯入版面，要使用任何版面，你必須在版面裡納入 `<meta charset="utf-8">` 標籤，因為 Astro 將不再自動將它加入 MDX 頁面。

<ReadMore>關於 Astro 對 Markdown 和 MDX 的支援，請參考 [Markdown 指南](/zh-tw/guides/markdown-content/)。</ReadMore>

## 巢狀版面

版面元件不需要包含整頁 HTML 內容。可以將版面拆成更小的元件，並搭配使用版面元件建立更彈性的頁面模板。在不同版面共用程式碼時，這個模式十分實用。

舉例來說，`BlogPostLayout.astro` 元件可以為部落格文章的標題、日期，以及作者設定樣式。接著，在整個站台共用的 `BaseLayout.astro` 可以處理剩下的頁面模板，像導覽列、頁尾、SEO meta 標籤、全域樣式、字型等。你也可以從文章接收參數，再傳遞到其他版面，就像跟其他巢狀元件互動一樣。

```astro {3} /</?BaseLayout>/ /</?BaseLayout url={frontmatter.url}>/
---
// src/layouts/BlogPostLayout.astro
import BaseLayout from './BaseLayout.astro';
const { frontmatter } = Astro.props;
---
<BaseLayout url={frontmatter.url}>
  <h1>{frontmatter.title}</h1>
  <h2>Post author: {frontmatter.author}</h2>
  <slot />
</BaseLayout>
```
